When a class has a constructor accepting initializer_list
of type X
and another constructor that has n parameters of
either type X
or a type that can be converted to X
, the constructor call resolution becomes complex. This makes code hard to
reason about and might lead to calls resolving to unexpected constructors. What makes it even more complex, is that the constructor resolution rules
are different if X
is a type template parameter.
This rule flags classes that have constructors overlapping with the initializer_list
constructor. It is recommended to simplify the
class by:
- A technical change: replace
initializer_list
parameter by a std::vector
, a std::array
, or a variadic
template. This way the caller is forced to be more explicit.
- A design change: make the construction of an object of type
X
taking object(s) of type Y
as parameters equivalent to
constructing it with an initializer list containing the object(s) of type Y
. This way you can reduce the number of overlapping
constructors to the one that takes initializer_list
.
Noncompliant code example
class A { // Noncompliant
public:
A();
A(int); // This constructor overlaps with the initializer_list constructor
A(int, long); // This constructor overlaps with the initializer_list constructor
A(std::initializer_list<int>); // "initializer_list" constructor
A(int, A);
A(int, double);
};
void f1() {
A a1(10); // A(int) is called
A a2{10}; // The "initializer_list" constructor is called
A a3(10, 1l); // A(int, long) is called
A a4{10, 1l}; // The "initializer_list" constructor is called
A a5{10, A{}}; // A(int, A) is called
// A a6{10, 1.2}; // doesn't compile
}
class B { // Noncompliant
public:
B(int); // This constructor overlaps with the initializer_list constructor
B(int, long); // This constructor doesn't overlap with the initializer_list constructor. See b4
template<typename T>
B(std::initializer_list<T>); // "initializer_list" constructor
};
void f2() {
B b1(10); // The constructor with single "int" parameter is called
B b2{10}; // The "initializer_list" constructor is called
B b3(10, 1l); // B(int, long) is called
B b4{10, 1l}; // B(int, long) is called
}
Compliant solution
class A {
public:
A();
A(int);
A(int, long);
A(std::vector<int>);
A(int, A);
A(int, double);
};
void f1() {
A a1(10); // A(int) is called
A a2{10}; // A(int) is called
A a3(10, 1l); // A(int, long) is called
A a4{10, 1l}; // A(int, long)
A a5{10, A{}}; // A(int, A) is called
A a6{10, 1.2}; // A(int, A) is called
A a7 {{1,2,4}}; // vector is called no confusion
}
class B {
public:
B(int, long);
template<typename T>
B(std::initializer_list<T>);
};
void f2() {
B b1({10}); // The "initializer_list" constructor is called
B b2{10}; // The "initializer_list" constructor is called
B b3(10, 1l); // B(int, long) is called
B b4{10, 1l}; // B(int, long) is called
}